﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MathExpressionSolver.App_Code
{
    public class Math_Functions
    {
        //*************************************************
        //              Class Members
        //*************************************************
        #region Class Members
        public const char L_ROUND_BRACKET = '(';
        public const char R_ROUND_BRACKET = ')';

        public const char L_SQUARE_BRACKET = '[';
        public const char R_SQUARE_BRACKET = ']';

        public const char PLUS_SYMBOL = '+';
        public const char MINUS_SYMBOL = '-';
        public const char MULTIPLICATION_SYMBOL = '*';
        public const char DIVISION_SYMBOL = '/';

        public const char LESS_THAN_SYMBOL = '<';
        public const char GREATER_THAN_SYMBOL = '>';
        public const char EQUALS_SYMBOL = '=';

        public const char LESS_OR_EQUALS_SYMBOL = '{';
        public const char GREATER_OR_EQUALS_SYMBOL = '}';
        public const char NOT_EQUALS_SYMBOL = '~';

        public const char AND_SYMBOL = '&';
        public const char OR_SYMBOL = '|';

        public const string LESS_OR_EQUALS_TEXT = "<=";
        public const string GREATER_OR_EQUALS_TEXT = ">=";
        public const string NOT_EQUALS_TEXT = "<>";

        public const string AND_TEXT = "[AND]";
        public const string OR_TEXT = "[OR]";

        public const char MULTI_VALUE_SPLITTER = '\'';

        private const char SPLITTER = '#';
        #endregion
        //*************************************************



        //*************************************************
        //                   Methods
        //*************************************************
        #region Methods
        /// <summary>
        /// Add two numbers together
        /// </summary>
        /// <param name="num1">First number to add</param>
        /// <param name="num2">Second number to add</param>
        /// <returns>The result of the summation for num1 + num2 or the value of '0' if an error occurs during the parsing of the numbers</returns>
        public static double PerformAddition(string num1, string num2)
        {
            double result = 0;

            if ((Validation_Functions.IsDecimal(num1)) && (Validation_Functions.IsDecimal(num2)))
                result = PerformAddition(double.Parse(num1), double.Parse(num2));
            
            return result;
        }

        /// <summary>
        /// Add two numbers together
        /// </summary>
        /// <param name="num1">First number to add</param>
        /// <param name="num2">Second number to add</param>
        /// <returns>The result of the summation for num1 + num2</returns>
        public static  double PerformAddition(double num1, double num2)
        {
            return num1 + num2;
        }


        /// <summary>
        /// Performs a simple mathematical operation between two numbers
        /// </summary>
        /// <param name="num1">First number to use</param>
        /// <param name="num2">Second number to use</param>
        /// <param name="math_operator">The mathematical operator (i.e. '+', '-', '*', '/')</param>
        /// <returns>The result of the mathematical operation or the value of '0' if an error occurs during the parsing of the numbers</returns>
        public static double SimpleEval(string num1, string num2, char math_operator)
        {
            double result = 0;

            if ((Validation_Functions.IsDecimal(num1)) && (Validation_Functions.IsDecimal(num2)))
                result = SimpleEval(double.Parse(num1), double.Parse(num2), math_operator);

            return result;
        }

        /// <summary>
        /// Performs a simple mathematical operation between two numbers
        /// </summary>
        /// <param name="num1">First number to use</param>
        /// <param name="num2">Second number to use</param>
        /// <param name="math_operator">The mathematical operator (i.e. '+', '-', '*', '/')</param>
        /// <returns>The result of the mathematical operation</returns>
        public static double SimpleEval(double num1, double num2, char math_operator)
        {
            double result = 0;

            switch (math_operator)
            {
                case PLUS_SYMBOL:
                    result = num1 + num2;
                    break;
                case MINUS_SYMBOL:
                    result = num1 - num2;
                    break;
                case MULTIPLICATION_SYMBOL:
                    result = num1 * num2;
                    break;
                case DIVISION_SYMBOL:
                    result = num1 / num2;
                    break;
            }

            return result;
        }


        /// <summary>
        /// Evaluates a mathematical expression
        /// </summary>
        /// <param name="expression">The mathematical expression to workout</param>
        /// <returns>The result of the mathematical expression</returns>
        public static double Eval(string expression)
        {
            double result = 0;

            expression = string.Format("({0})", expression);

            if (CheckBracketCounts(expression))
            {
                while (expression.Contains(R_ROUND_BRACKET.ToString()))
                {
                    int start_index = expression.LastIndexOf(L_ROUND_BRACKET) + 1;
                    int end_index = expression.Substring(start_index).IndexOf(R_ROUND_BRACKET) + start_index;

                    if (start_index < end_index)
                    {
                        string exp = expression.Substring(start_index, (end_index - start_index));

                        result = Evaluate(exp);

                        expression = expression.Remove(start_index, (end_index - start_index));
                        expression = expression.Replace(string.Format("{0}{1}", L_ROUND_BRACKET, R_ROUND_BRACKET), result.ToString());

                        exp = string.Empty;
                    }
                }
            }

            return result;
        }

        private static double Evaluate(string expression)
        {
            double result = 0;

            string equation = string.Format("0{0}", expression);

            equation = equation.Replace(PLUS_SYMBOL, SPLITTER);
            equation = equation.Replace(MINUS_SYMBOL, SPLITTER);
            equation = equation.Replace(MULTIPLICATION_SYMBOL, SPLITTER);
            equation = equation.Replace(DIVISION_SYMBOL, SPLITTER);

            string[] nums = equation.Split(SPLITTER);
            char[] operators = new char[nums.Length - 1];

            int count = 0;
            for (int i = 0; i < expression.Length; i++)
            {
                if ((expression[i].Equals(PLUS_SYMBOL)) || (expression[i].Equals(MINUS_SYMBOL)) || (expression[i].Equals(MULTIPLICATION_SYMBOL)) || (expression[i].Equals(DIVISION_SYMBOL)))
                {
                    operators[count] = expression[i];
                    count++;
                }
            }

            for (int i = 0; i < nums.Length; i++)
            {
                if (nums[i].Length == 0)
                    nums[i] = "0";
            }

            result = double.Parse(nums[0]);
            for (int i = 0; i < operators.Length; i++)
            {
                switch (operators[i])
                {
                    case PLUS_SYMBOL:
                        result += double.Parse(nums[i + 1]);
                        break;
                    case MINUS_SYMBOL:
                        result -= double.Parse(nums[i + 1]);
                        break;
                    case MULTIPLICATION_SYMBOL:
                        result *= double.Parse(nums[i + 1]);
                        break;
                    case DIVISION_SYMBOL:
                        result /= double.Parse(nums[i + 1]);
                        break;
                }
            }

            return result;
        }


        /// <summary>
        /// Evaluates a mathematical condition
        /// </summary>
        /// <param name="expression">The mathematical condition to evaluate</param>
        /// <returns>'True' if the condition is met, 'False' otherwise</returns>
        public static bool EvalCompare(string condition)
        {
            bool is_equal = false;

            condition = string.Format("({0})", condition);

            if ((CheckBracketCounts(condition)) && (HasCompareOperands(condition)))
            {
                while (condition.Contains(R_ROUND_BRACKET.ToString()))
                {
                    int start_index = condition.LastIndexOf(L_ROUND_BRACKET) + 1;
                    int end_index = condition.Substring(start_index).IndexOf(R_ROUND_BRACKET) + start_index;

                    if (start_index < end_index)
                    {
                        string exp = condition.Substring(start_index, (end_index - start_index));

                        if ((HasMathOperands(exp)) && (!MinusSymbolException(exp)))
                        {
                            double result = Eval(exp);

                            condition = condition.Remove(start_index, (end_index - start_index));
                            condition = condition.Replace(string.Format("{0}{1}", L_ROUND_BRACKET, R_ROUND_BRACKET), result.ToString());
                        }
                        else
                        {
                            if (Validation_Functions.IsBoolean(exp))
                                is_equal = bool.Parse(exp);
                            else
                                is_equal = EvaluateCompare(exp);

                            condition = condition.Remove(start_index, (end_index - start_index));
                            condition = condition.Replace(string.Format("{0}{1}", L_ROUND_BRACKET, R_ROUND_BRACKET), is_equal.ToString());
                        }
                        exp = string.Empty;
                    }
                }
            }

            return is_equal;
        }

        private static bool EvaluateCompare(string condition)
        {
            bool result = false;

            condition = condition.Replace(AND_TEXT, AND_SYMBOL.ToString());
            condition = condition.Replace(OR_TEXT, OR_SYMBOL.ToString());
            condition = condition.Replace(GREATER_OR_EQUALS_TEXT, GREATER_OR_EQUALS_SYMBOL.ToString());
            condition = condition.Replace(LESS_OR_EQUALS_TEXT, LESS_OR_EQUALS_SYMBOL.ToString());
            condition = condition.Replace(NOT_EQUALS_TEXT, NOT_EQUALS_SYMBOL.ToString());

            string equation = condition;

            equation = equation.Replace(GREATER_THAN_SYMBOL, SPLITTER);
            equation = equation.Replace(LESS_THAN_SYMBOL, SPLITTER);
            equation = equation.Replace(EQUALS_SYMBOL, SPLITTER);
            equation = equation.Replace(GREATER_OR_EQUALS_SYMBOL, SPLITTER);
            equation = equation.Replace(LESS_OR_EQUALS_SYMBOL, SPLITTER);
            equation = equation.Replace(NOT_EQUALS_SYMBOL, SPLITTER);
            equation = equation.Replace(AND_SYMBOL, SPLITTER);
            equation = equation.Replace(OR_SYMBOL, SPLITTER);

            string[] nums = equation.Split(SPLITTER);
            char[] conditions = new char[nums.Length - 1];

            int count = 0;
            for (int i = 0; i < condition.Length; i++)
            {
                switch (condition[i])
                {
                    case GREATER_THAN_SYMBOL:
                    case LESS_THAN_SYMBOL:
                    case EQUALS_SYMBOL:
                    case GREATER_OR_EQUALS_SYMBOL:
                    case LESS_OR_EQUALS_SYMBOL:
                    case NOT_EQUALS_SYMBOL:
                    case AND_SYMBOL:
                    case OR_SYMBOL:
                        conditions[count] = condition[i];
                        count++;
                        break;
                }
            }

            for (int i = 0; i < conditions.Length; i++)
            {
                if ((!nums[i].Contains(MULTI_VALUE_SPLITTER.ToString())) && (!nums[i + 1].Contains(MULTI_VALUE_SPLITTER.ToString())))
                {
                    result = Compare(nums[i], nums[i + 1], conditions[i]);
                }
                else if ((nums[i].Contains(MULTI_VALUE_SPLITTER.ToString())) && (!nums[i + 1].Contains(MULTI_VALUE_SPLITTER.ToString())))
                {
                    string[] inner_nums = nums[i].Split(MULTI_VALUE_SPLITTER);
                    foreach (string inner_num in inner_nums)
                    {
                        result = Compare(inner_num, nums[i + 1], conditions[i]);
                        if (result)
                            break;
                    }
                }
                else if ((!nums[i].Contains(MULTI_VALUE_SPLITTER.ToString())) && (nums[i + 1].Contains(MULTI_VALUE_SPLITTER.ToString())))
                {
                    string[] inner_nums = nums[i + 1].Split(MULTI_VALUE_SPLITTER);
                    foreach (string inner_num in inner_nums)
                    {
                        result = Compare(nums[i], inner_num, conditions[i]);
                        if (result)
                            break;
                    }
                }
            }

            return result;
        }

        private static bool Compare(string value1, string value2, char operand)
        {
            bool result = false;

            if ((value1.Length > 0) && (value2.Length > 0))
            {
                switch (operand)
                {
                    case GREATER_THAN_SYMBOL:
                        result = (double.Parse(value1) > double.Parse(value2));
                        break;
                    case LESS_THAN_SYMBOL:
                        result = (double.Parse(value1) < double.Parse(value2));
                        break;
                    case EQUALS_SYMBOL:
                        result = (double.Parse(value1) == double.Parse(value2));
                        break;
                    case GREATER_OR_EQUALS_SYMBOL:
                        result = (double.Parse(value1) >= double.Parse(value2));
                        break;
                    case LESS_OR_EQUALS_SYMBOL:
                        result = (double.Parse(value1) <= double.Parse(value2));
                        break;
                    case NOT_EQUALS_SYMBOL:
                        result = (double.Parse(value1) != double.Parse(value2));
                        break;
                    case AND_SYMBOL:
                        result = (bool.Parse(value1) && bool.Parse(value2));
                        break;
                    case OR_SYMBOL:
                        result = (bool.Parse(value1) || bool.Parse(value2));
                        break;
                }
            }
            else if ((value1.Length > 0) && (value2.Length == 0))
            {
                switch (operand)
                {
                    case EQUALS_SYMBOL:
                        result = false;
                        break;
                    case NOT_EQUALS_SYMBOL:
                        result = true;
                        break;
                    case LESS_OR_EQUALS_SYMBOL:
                    case GREATER_OR_EQUALS_SYMBOL:
                        result = false;
                        break;
                }
            }
            else if ((value1.Length == 0) && (value2.Length > 0))
            {
                switch (operand)
                {
                    case EQUALS_SYMBOL:
                        result = false;
                        break;
                    case NOT_EQUALS_SYMBOL:
                        result = true;
                        break;
                    case LESS_OR_EQUALS_SYMBOL:
                    case GREATER_OR_EQUALS_SYMBOL:
                        result = false;
                        break;
                }
            }
            else if ((value1.Length == 0) && (value2.Length == 0))
            {
                switch (operand)
                {
                    case EQUALS_SYMBOL:
                        result = true;
                        break;
                    case NOT_EQUALS_SYMBOL:
                        result = false;
                        break;
                    case LESS_OR_EQUALS_SYMBOL:
                    case GREATER_OR_EQUALS_SYMBOL:
                        result = true;
                        break;
                }
            }

            return result;
        }


        private static bool CheckBracketCounts(string expression)
        {
            int r_bracket_count = 0;
            int l_bracket_count = 0;

            foreach (char c in expression)
            {
                switch (c)
                {
                    case R_ROUND_BRACKET:
                        r_bracket_count++;
                        break;
                    case L_ROUND_BRACKET:
                        l_bracket_count++;
                        break;
                }
            }

            return (r_bracket_count == l_bracket_count);
        }

        private static bool HasMathOperands(string expression)
        {
            return HasMathOperands(expression, true);
        }

        private static bool HasMathOperands(string expression, bool include_minus)
        {
            return ((expression.Contains(PLUS_SYMBOL.ToString())) || ((include_minus) && (expression.Contains(MINUS_SYMBOL.ToString()))) || (expression.Contains(MULTIPLICATION_SYMBOL.ToString())) || (expression.Contains(DIVISION_SYMBOL.ToString())));
        }

        private static bool HasCompareOperands(string expression)
        {
            return ((expression.Contains(LESS_THAN_SYMBOL.ToString())) || (expression.Contains(GREATER_THAN_SYMBOL.ToString())) || (expression.Contains(EQUALS_SYMBOL.ToString())) || (expression.Contains(LESS_OR_EQUALS_TEXT)) || (expression.Contains(GREATER_OR_EQUALS_TEXT)) || (expression.Contains(NOT_EQUALS_TEXT)));
        }

        private static bool MinusSymbolException(string expression)
        {
            return ((expression.Contains(LESS_THAN_SYMBOL.ToString() + MINUS_SYMBOL.ToString())) || (expression.Contains(GREATER_THAN_SYMBOL.ToString() + MINUS_SYMBOL.ToString())) || (expression.Contains(EQUALS_SYMBOL.ToString() + MINUS_SYMBOL.ToString())) || (expression.Contains(LESS_OR_EQUALS_TEXT + MINUS_SYMBOL.ToString())) || (expression.Contains(GREATER_OR_EQUALS_TEXT + MINUS_SYMBOL.ToString())) || (expression.Contains(NOT_EQUALS_TEXT + MINUS_SYMBOL.ToString())) || ((expression.StartsWith(MINUS_SYMBOL.ToString())) && (!HasMathOperands(expression, false))));
        }
        #endregion
        //*************************************************
    }
}